package iweb

import (
	"gitee.com/tietang/terrace-go/v2/base"
	"github.com/bmatcuk/doublestar"
	"github.com/patrickmn/go-cache"
	log "github.com/sirupsen/logrus"
	"github.com/tietang/props/v3/kvs"
	"github.com/xi2/httpgzip"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type StaticWebServerStarter struct {
	base.BaseStarter
}

func (s *StaticWebServerStarter) Init(ctx base.StarterContext) {
	conf := ctx.Props()
	injectEnv(conf)
}
func (s *StaticWebServerStarter) Start(ctx base.StarterContext) {
	conf := ctx.Props()
	injectEnv(conf)
	healthAndInfoEndpoint(conf, http.DefaultServeMux)
	staticCache := cache.New(24*time.Hour, 240*time.Hour)

	dirs := conf.Strings("ui.static.dirs")
	staticHandler := &StaticHandler{
		dirs:        dirs,
		staticCache: staticCache,
	}
	http.Handle("/", httpgzip.NewHandler(staticHandler, nil))

	for _, dir := range dirs {
		http.Handle("/"+dir, http.FileServer(http.Dir("./"+dir)))
	}

	port := conf.GetDefault("app.port", "18088")

	log.Info("server port：http://127.0.0.1:", port)
	log.Error(http.ListenAndServe(":"+port, nil))

}

func injectEnv(conf kvs.ConfigSource) {
	pwd, _ := os.Getwd()
	log.Info("inject dir: ", pwd)
	files := conf.Strings("ac.domain.switch.files")
	apiPlaceholder := conf.GetDefault("ac.domain.placeholder.api", "http://devadmin.tietang.wang")
	staticPlaceholder := conf.GetDefault("ac.domain.placeholder.static", "http://devadmin.tietang.wang/console")
	apiDomain := conf.GetDefault("ac.domain.api", "http://devadmin.tietang.wang")
	staticDomain := conf.GetDefault("ac.domain.static", "http://devadmin.tietang.wang/console")
	log.Info("replace: ", apiPlaceholder, " to ", apiDomain)
	log.Info("replace: ", staticPlaceholder, " to ", staticDomain)
	for _, file := range files {
		f := filepath.Join(pwd, file)
		log.Info("pre inject file: ", f)
		dir := filepath.Dir(f)
		fileInfos, err := ioutil.ReadDir(dir)
		if err != nil {
			log.Error(err)
			continue
		}
		for _, fi := range fileInfos {
			if !fi.IsDir() {
				f2 := filepath.Join(dir, fi.Name())
				if ok, e := filepath.Match(f, f2); ok && e == nil {
					log.Info("process file: ", f2)
					Replace(f2, []string{apiPlaceholder, staticPlaceholder}, []string{apiDomain, staticDomain})
				}
			}
		}
	}
}

type StaticHandler struct {
	dirs        []string
	staticCache *cache.Cache
}

func (g *StaticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	uriPath := r.URL.Path

	dirs := g.dirs
	staticCache := g.staticCache

	uriPath = strings.TrimPrefix(uriPath, "/console")
	cached := false
	file := "/index.html"
	for _, dir := range dirs {
		p := strings.TrimPrefix(uriPath, "/")
		if strings.HasPrefix(p, dir) || isExcluded(uriPath) {
			file = uriPath
		}
	}
	ext := filepath.Ext(file)
	//排除html不304，其他静态文件，如果不是第一次请求，304
	if ext != ".html" && ext != ".htm" {
		if status := isWriteModified(w, r); status > 0 {
			//log.Info(ext, " ", uriPath, " ", file, " ", 304)
			w.WriteHeader(status)
			return
		}
	}

	switch {
	case strings.HasSuffix(uriPath, ".css"):
		w.Header().Set("content-type", "text/css")
	case strings.HasSuffix(uriPath, ".js"):
		w.Header().Set("content-type", "text/javascript")
	default:
	}
	v, ok := staticCache.Get(file)
	status := http.StatusOK
	var fd []byte
	if ok {
		fd = v.([]byte)
		cached = true
		status = http.StatusOK
	} else {

		fd, ok = fileWrite(file, w, r)
		cached = false
		if ok {
			staticCache.Set(file, fd, -1)

			status = http.StatusOK
		} else {
			status = http.StatusNotFound
			w.WriteHeader(status)
		}
	}
	log.WithFields(log.Fields{
		"status":   status,
		"cached":   cached,
		"URL":      uriPath,
		"filePath": file,
	}).Info("Access: ")
	w.Write(fd)
}

func isWriteModified(w http.ResponseWriter, r *http.Request) int {
	v := r.Header.Get("If-Modified-Since")
	if v == "" {
		w.Header().Add("Last-Modified", time.Now().Format(GMT))
	} else {
		return http.StatusNotModified
	}
	return -1
}
func fileWrite(file string, w http.ResponseWriter, r *http.Request) ([]byte, bool) {
	fin, err := os.Open("." + file)
	defer fin.Close()
	pwd, _ := os.Getwd()
	ok := true
	if err != nil {
		log.Error("open file:", err, pwd, file)
		ok = false
	}
	fd, err := ioutil.ReadAll(fin)
	if err != nil {
		log.Error("read file:", err, pwd, file)
		ok = false
	}
	if ok {
		return fd, true
	} else {
		return []byte("not found file."), false
	}
}

func isExcluded(urlPath string) bool {
	for _, k := range Exclueds {
		ok, _ := doublestar.PathMatch(k, urlPath)
		if ok {
			return true
		}
	}
	return false
}
